package com.beta.cat.config.dubbo2;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration;
import com.alibaba.cloud.dubbo.metadata.RevisionResolver;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.DubboCloudRegistry;
import com.alibaba.cloud.dubbo.registry.MetadataServiceSubscribeHandler;
import com.alibaba.cloud.dubbo.registry.ReSubscribeManager;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancesChangedEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.support.AbstractRegistryFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.springframework.util.StringUtils.hasText;

/**
 * 启用心跳事件的事件发布，保证心跳得到的信息及时更新。防止subscribe无故断开后的急救行为
 */
@Slf4j
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = {"org.springframework.cloud.client.discovery.DiscoveryClient", "com.alibaba.cloud.dubbo.registry.DubboCloudRegistry"})
@ConditionalOnProperty(name = "spring.cloud.discovery.enabled", matchIfMissing = true)
@AutoConfigureAfter(value = {DubboServiceDiscoveryAutoConfiguration.class})
public class DubboHeartbeatRefreshServicesAutoConfiguration {
    private final DubboServiceMetadataRepository dubboServiceMetadataRepository;

    private final ApplicationEventPublisher applicationEventPublisher;

    private final DiscoveryClient discoveryClient;

    @Value("${spring.application.name:${dubbo.application.name:application}}")
    private String currentApplicationName;


    public DubboHeartbeatRefreshServicesAutoConfiguration(DubboServiceMetadataRepository dubboServiceMetadataRepository, ApplicationEventPublisher applicationEventPublisher, DiscoveryClient discoveryClient) {
        this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
        this.applicationEventPublisher = applicationEventPublisher;
        this.discoveryClient = discoveryClient;
    }

    @EventListener(HeartbeatEvent.class)
    public void onHeartbeatEvent(HeartbeatEvent event) {//开启心跳事件接收,以防止订阅的长链接失效而导致client被释放再也找不到的现象
        Stream<String> subscribedServices = dubboServiceMetadataRepository.initSubscribedServices();
        subscribedServices.forEach(serviceName -> {
            List<ServiceInstance> serviceInstances = getInstances(serviceName);
            dispatchServiceInstancesChangedEvent(serviceName, serviceInstances);
        });
    }

    @EventListener(ServiceInstancesChangedEvent.class)
    public void onServiceInstancesChangedEvent(ServiceInstancesChangedEvent event) {
        //强制更新
        Collection<Registry> registers = AbstractRegistryFactory.getRegistries();
        if (registers == null || registers.isEmpty()) {
            return;
        }
        String appName = event.getServiceName();
        List<ServiceInstance> instances = getInstances(appName);
        instances = instances != null ? instances : Collections.emptyList();
        Map<String, List<ServiceInstance>> newGroup = instances.stream().collect(Collectors.groupingBy(RevisionResolver::getRevision));
        for (Registry registry : registers) {
            if (!(registry instanceof DubboCloudRegistry)) {
                continue;
            }
            DubboCloudRegistry dubboCloudRegistry = (DubboCloudRegistry) registry;

            try {
                Map<String, MetadataServiceSubscribeHandler> metadataSubscribeHandlerMap =
                        (Map<String, MetadataServiceSubscribeHandler>) FieldUtils.readField(dubboCloudRegistry, "metadataSubscribeHandlerMap", true);
                MetadataServiceSubscribeHandler handler = metadataSubscribeHandlerMap
                        .get(appName);
                if (handler == null) {
                    continue;
                }
            } catch (IllegalAccessException e) {
                log.debug(e.getMessage(), e);
            }
            // 更新订阅的信息
//            Map<URL, Set<NotifyListener>> subscribed = dubboCloudRegistry.getSubscribed();
//            for (URL subscribedURL : subscribed.keySet()) {
//                String serviceInterface = subscribedURL.getServiceInterface();
//                String version = subscribedURL.getParameter(CommonConstants.VERSION_KEY);
//                String protocol = subscribedURL.getParameter(CommonConstants.PROTOCOL_KEY);
//                List<URL> urls = dubboMetadataUtils.getDubboMetadataServiceURLs(instances,
//                        serviceInterface, version, protocol);
//                subscribed.get(subscribedURL).forEach(listener -> {
//                    listener.notify(urls);
//                });
//            }

            Map<String, List<ServiceInstance>> oldGroup = dubboCloudRegistry.getServiceRevisionInstanceMap().computeIfAbsent(appName, k -> new HashMap<>());
            try {
                //如果相同，就不更新
                if (serviceInstanceNotChanged(oldGroup, newGroup)) {
                    return;
                }
                log.info("app {} instances new Group:{}, old Group:{}", appName, instancesToString(newGroup), instancesToString(oldGroup));
                // ensure that the service metadata is correct
                MethodUtils.invokeMethod(dubboCloudRegistry, true, "refreshServiceMetadataInfo", appName, instances);
//                dubboCloudRegistry.refreshServiceMetadataInfo(appName, instances);

                // then , refresh general service associated with current application
                MethodUtils.invokeMethod(dubboCloudRegistry, true, "refreshGeneralServiceInfo", appName, oldGroup, newGroup);
//                dubboCloudRegistry.refreshGeneralServiceInfo(appName, oldGroup, newGroup);

                // mark process successful
                ((ReSubscribeManager) FieldUtils.readField(dubboCloudRegistry, "reSubscribeManager", true)).onRefreshSuccess(event);

            } catch (Exception e) {
                log.error(String.format(
                        "APP %s instance changed, handler faild, try resubscribe",
                        appName), e);
                try {
                    ((ReSubscribeManager) FieldUtils.readField(dubboCloudRegistry, "reSubscribeManager", true)).onRefreshFail(event);
                } catch (Exception ex) {
                    log.warn(ex.getMessage(), ex);
                }
            }
        }
    }

    private boolean serviceInstanceNotChanged(Map<String, List<ServiceInstance>> oldGroup,
                                              Map<String, List<ServiceInstance>> newGroup) {
        if (newGroup.size() != oldGroup.size()) {
            return false;
        }

        for (Map.Entry<String, List<ServiceInstance>> entry : newGroup.entrySet()) {
            String appName = entry.getKey();
            List<ServiceInstance> newInstances = entry.getValue();

            if (!oldGroup.containsKey(appName)) {
                return false;
            }

            List<ServiceInstance> oldInstances = oldGroup.get(appName);
            if (newInstances.size() != oldInstances.size()) {
                return false;
            }

            boolean matched = newInstances.stream().allMatch(newInstance -> {

                for (ServiceInstance oldInstance : oldInstances) {
                    if (instanceSame(newInstance, oldInstance)) {
                        return true;
                    }
                }

                return false;
            });
            if (!matched) {
                return false;
            }
        }

        return true;
    }

    private boolean instanceSame(ServiceInstance newInstance,
                                 ServiceInstance oldInstance) {
        if (!StringUtils.equals(newInstance.getInstanceId(),
                oldInstance.getInstanceId())) {
            return false;
        }
        if (!StringUtils.equals(newInstance.getHost(), oldInstance.getHost())) {
            return false;
        }
        if (!StringUtils.equals(newInstance.getServiceId(), oldInstance.getServiceId())) {
            return false;
        }
        if (!StringUtils.equals(newInstance.getScheme(), oldInstance.getScheme())) {
            return false;
        }
        if (oldInstance.getPort() != newInstance.getPort()) {
            return false;
        }

        if (!oldInstance.getMetadata().equals(newInstance.getMetadata())) {
            return false;
        }

        return true;
    }

    private String instancesToString(Map<String, List<ServiceInstance>> group) {
        if (CollectionUtils.isEmpty(group)) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (String name : group.keySet()) {
            List<ServiceInstance> instances = group.get(name);
            if (sb.length() > 0) {
                sb.append(", ");
            }
            sb.append(name + "=");
            if (instances == null || instances.isEmpty()) {
                sb.append("[]");
                continue;
            }
            sb.append("[");
            for (ServiceInstance instance : instances) {
                sb.append(instance.getHost() + ":" + instance.getPort() + ",");
            }
            sb.replace(sb.length() - 1, sb.length(), "]");
        }
        sb.insert(0, "[");
        sb.append(" ]");
        return sb.toString();
    }

    private List<ServiceInstance> getInstances(String serviceName) {
        return discoveryClient.getInstances(serviceName);
    }

    private void dispatchServiceInstancesChangedEvent(String serviceName, List<ServiceInstance> serviceInstances) {
        if (!hasText(serviceName) || Objects.equals(currentApplicationName, serviceName) || serviceInstances == null) {
            return;
        }

        ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent(serviceName, serviceInstances);
//        if (log.isInfoEnabled()) {
//            log.info("The event of the service instances[name : {} , size : {}] change is about to be dispatched", serviceName, serviceInstances.size());
//        }
        applicationEventPublisher.publishEvent(event);
    }
}
